/***********************************************************************************************************************
 *该模块只负责代码的运行工作
 *主要逻辑
 *1.为文件名生成对应的可执行文件名，标准输入，输出，错误重定向的文件名，并使用open函数打开输入，输出，错误文件，权限为644
 *2.使用fork函数创建子进程去实现文件的执行，主进程等待子进程的结果
 *3.子进程先对文件描述符进行重定向，然后设置进程的资源使用情况使用execl进行程序替换，执行.exe文件
 *4.主进程关闭打开的文件描述符，等待子进程结果，并且返回子进程退出时的信号，>0则说明进程异常结束，=0正常结束，<0内部错误
 ***********************************************************************************************************************/
#pragma once
#include "../Comm/comm.hpp"
#include "../Comm/log.hpp"
#include "../Comm/util.hpp"

namespace ns_runner
{
    using namespace ns_log;
    using namespace ns_util;
    class Runner
    {

    public:
        Runner()
        {
        }
        ~Runner()
        {
        }
        // 此函数为为进程设置系统资源的使用上限
        static void SetResouce(int cpu_limit, int mem_limit)
        {
            // 设置cpu资源
            struct rlimit cpu_resource;
            cpu_resource.rlim_max = RLIM_INFINITY;
            cpu_resource.rlim_cur = cpu_limit;
            setrlimit(RLIMIT_CPU, &cpu_resource);

            // 设置内存资源
            struct rlimit mem_resource;
            mem_resource.rlim_max = RLIM_INFINITY;
            mem_resource.rlim_cur = mem_limit * 1024; // 转为kb
            setrlimit(RLIMIT_AS, &mem_resource);
        }
        /****************************
         *只需要传递文件名即可，不需要带后缀
         * 返回值为int
         * 返回值 > 0: 程序异常，信号被设置，退出时收到了信号，返回值就是信号
         * 返回值 = 0: 程序正常执行完毕，结果将保存到对应的临时文件中
         * 返回值 < 0: 程序内部错误
         * cpu_limit: 进程cpu的限制资源
         * mem_limit: 进程内存的限制资源(单位为kb)
         *****************************/
        // 此函数为运行编译阶段生成的可执行文件
        static int Run(const std::string &file_name, int cpu_limit, int mem_limit)
        {
            /***********************
             * 程序运行无非三种错误
             * 1.代码跑完，结果正确
             * 2.代码跑完，结果错误
             * 3.代码没跑完，异常了
             * Run只关注代码有没有跑完，并不关注结果正确与否
             * 结果的判定由测试用例判断
             * file_name: 告诉我们执行文件是谁
             * 标准输入:不处理
             * 标准输出:重定向到自己文件，显出输出结果
             * 标准错误:重定向到自己文件，查看运行时的错误信息
             ************************/

            // 生成对应的文件名，执行文件，标准输出，标准输入，标准错误
            std::string _execute = PathUtil::Exe(file_name);
            std::string _stdin = PathUtil::Stdin(file_name);
            std::string _stdout = PathUtil::Stdout(file_name);
            std::string _stderr = PathUtil::Stderr(file_name);

            // 打开文件生成的文件
            umask(0);
            int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY, 0644); // rw_r__r__
            int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);

            // 任何一个文件打开失败，后续都无法进行
            if (_stderr_fd < 0 || _stdin_fd < 0 || _stdout_fd < 0)
            {
                LOG(ERROR) << "运行错误，标准文件未打开" << endl;
                return -1;
            }

            // 创建子进程执行程序替换进行运行
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "运行错误，创建子进程失败" << endl;
                // 先关闭打开的文件
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                return -2;
            }
            else if (pid == 0)
            {
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);

                SetResouce(cpu_limit, mem_limit);
                
                execl(_execute.c_str()/*我要执行谁*/, _execute.c_str()/*我想在命令行上如何执行该程序*/, nullptr);
                exit(1);
            }
            else
            {
                // 主进程关闭文件描述符
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                // 主进程等待子进程运行结果
                int status = 0;
                waitpid(pid, &status, 0);
                LOG(NORMAL) << "运行完毕，NORMAL：" << (status & 0x7F) << endl;
                return status & 0x7F;
            }
        }
    };
}
